03 - 压缩与裁剪
本篇回答:已经在上下文里的东西怎么砍掉。两种砍法的语义完全不同,代价也完全不同。
本篇会用到的词:
| 词 | 意思 |
|---|---|
| 压缩(compaction) | 把旧内容总结成一段摘要,用摘要替换原文。语义保留,细节丢失 |
| 裁剪(context editing) | 直接删掉特定内容块,替换成一句占位文本。不做总结,删了就是删了 |
| compaction 块 | 压缩产生的一个内容块,装着摘要。它必须被回传,否则压缩状态丢失 |
| 采样迭代 | 一次 API 调用内部可能发生多次模型采样。压缩就是一次额外的采样迭代 |
| 占位文本 | 裁剪后留在原位的一句说明,告诉模型"这里原本有内容,已被移除" |
一、两种砍法
二、服务端压缩
2.1 机制
const response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-5",
max_tokens: 16000,
messages,
context_management: {
edits: [{ type: "compact_20260112" }],
},
});
// ⚠️ 必须追加完整的 response.content,不能只取里面的文本块。
// 压缩产生的 compaction 块就在 content 里,API 靠它在下一次请求时
// 丢弃摘要之前的全部内容。只 append 文本 = 压缩状态静默丢失,
// 表现是"开了压缩但上下文还在无限增长",且不报任何错。
messages.push({ role: "assistant", content: response.content });
流程是四步:检测到输入 token 达到阈值 → 生成摘要 → 产生一个 compaction 块 → 带着压缩后的上下文继续这次回答。后续请求里,API 自动丢弃 compaction 块之前的所有内容块。
2.2 全部参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
type | string | 必填 | 必须是 "compact_20260112" |
trigger | object | {"type": "input_tokens", "value": 150000} | 何时触发。input_tokens 是唯一支持的类型,value 最低 50,000 |
pause_after_compaction | boolean | false | 生成摘要后是否暂停,交回控制权 |
instructions | string | null | 自定义总结提示词。提供时完全替换默认提示词,不是追加 |
2.3 三个坑
instructions 一段明确禁止调工具的提示词。注意 instructions 是完全替换默认提示词的,所以自定义的那段里必须同时写清"要在摘要里保留什么",否则会连默认提示词里的那些要求一起丢掉。第③个坑的量级值得单独看一眼。官方文档给的示例响应:
三、工具结果裁剪
对重工具调用的 Agent,这是比压缩更对症的手段 —— 01 篇那张构成图里,第 40 轮 82% 的占用都在工具结果上。
const response = await client.beta.messages.create({
betas: ["context-management-2025-06-27"],
model: "claude-opus-5",
max_tokens: 16000,
tools,
messages,
context_management: {
edits: [{
type: "clear_tool_uses_20250919",
// 触发阈值:可以按 input_tokens 也可以按 tool_uses 计
trigger: { type: "input_tokens", value: 80000 },
// 保留最近几次工具调用/结果对。留太少会让模型重复调同一个工具
keep: { type: "tool_uses", value: 5 },
// 至少要清掉这么多才执 行 —— 否则不值得打破前缀缓存(06 篇)
clear_at_least: { type: "input_tokens", value: 20000 },
// 这些工具的结果永不清理:小而关键、清了就得重调的那些
exclude_tools: ["read_project_config", "get_current_task"],
// 只清结果、保留 Claude 当初的调用参数(默认行为)。
// 设成 true 会连参数一起清,模型会看不出自己调过什么,容易重复调用
clear_tool_inputs: false,
}],
},
});
全部配置项:
| 配置项 | 默认 | 说明 |
|---|---|---|
trigger | 100,000 输入 token | 何时激活。可按 input_tokens 或 tool_uses 指定 |
keep | 3 次工具调用 | 清理后保留最近几对工具调用/结果,最旧的先删 |
clear_at_least | 无 | 每次至少要清掉这么多 token,否则本次不执行 |
exclude_tools | 无 | 这些工具的调用与结果永不被清 |
clear_tool_inputs | false | 是否连工具调用参数一起清。默认只清结果 |
3.1 clear_at_least 是为缓存存在的
官方文档在讲缓存那一节直接点明了:工具结果裁剪会让缓存前缀失效,所以要"清掉足够多的 token,让这次缓存失效变得值得"。
算术很简单:缓存读取约是常规输入价格的 1/10,缓存写入约 1.25 倍。清掉 2,000 token 却让 80,000 token 的前缀重新走缓存写入,是净亏。clear_at_least 就是把这个判断交给 API 去做。
没设这个参数是最常见的配置错误 —— 默认值是"无",也就是每次达到阈值就清,不管 清得值不值。
3.2 exclude_tools 该放什么
判据是"清掉之后模型会不会重新调一遍":
- 该排除:项目配置、当前任务描述、用户身份这类小而关键的结果。它们只有几百 token,但清掉之后模型会重新调用,反而更贵
- 不该排除:文件内容、搜索结果这类大块头。它们正是要清的对象
四、思考块裁剪
clear_thinking_20251015 管的是扩展思考产生的 thinking 块。它有一个特别容易踩的地方:默认行为按模型档次不同。
| 模型档次 | 保留全部历史思考 | 只保留最后一轮的思考 |
|---|---|---|
| Opus | Claude Opus 4.5 及之后 | Claude Opus 4.1 及之前 |
| Sonnet | Claude Sonnet 4.6 及之后 | Claude Sonnet 4.5 及之前 |
| Haiku | (无) | 到 Claude Haiku 4.5 为止的全部型号 |
官方的建议很明确:如果你的代码会跨多个模型档次运行,就显式设置 keep,不要依赖各档的默认值。否则同一份代码在不同模型上的上下文行为不一样,排查时会非常难受。
思考块裁剪和缓存的关系与工具结果裁剪相反:思考块被保留时缓存是保住的,被清理时才在清理点失效。所以 keep 这个参数实际上 是在"缓存命中率"和"上下文空间"之间做取舍。
五、阈值怎么定
六、什么时候不该压缩
- 需要精确回溯的任务。压缩是有损的,审计、合规、法务场景下摘要不能替代原文。这类场景该走卸载:原文存到外部,上下文里只留索引
- 单轮或少轮任务。压缩的阈值都在几万 token 以上,短任务永远触发不到,开了只是多一个配置项
- 对首字延迟极敏感的场景。触发压缩的那一轮会多一次完整的模型采样,用户会感觉到一次明显的卡顿。可以用
pause_after_compaction把控制权拿回来,在自己这边给个提示 - 成本敏感且工具结果占大头的场景。这时候裁剪比压缩合适得多 —— 总结一堆早就用完的文件内容,是花钱买一份没人会看的摘要
七、小结
- 压缩留语义丢细节、要多花一次完整模型调用 ;裁剪直接删、不花钱但信息全失。重工具调用的 Agent 优先用裁剪
messages.push必须追加完整的response.content,只取文本会让压缩状态静默丢失- 请求里带
tools时压缩可能失败(compaction块content: null),要用instructions明确禁止调工具 - 顶层
input_tokens不含压缩迭代,算成本必须遍历usage.iterations—— 否则会同时看到"token 下降"和"账单上涨" clear_at_least是为缓存存在的,不设它等于每次达到阈值就无条件破一次缓存- 跨模型档次运行时,思考块裁剪的
keep必须显式设置 - 需要精确回溯、单轮任务、首字延迟敏感这三种情况不该用压缩
下一篇:04 - 卸载到外部,第二类手段 —— 不砍,挪出去。
← 回到 专题索引